gh-153785: Generate AttributeError messages from context#153786
gh-153785: Generate AttributeError messages from context#153786johnslavik wants to merge 45 commits into
AttributeError messages from context#153786Conversation
This comment was marked as resolved.
This comment was marked as resolved.
AttributeError messages from contextAttributeError messages from context
Documentation build overview
37 files changed ·
|
I'm not sure how to create a nameless module at runtime
This reverts commit 7378e30.
|
|
||
| When possible, :attr:`name` and :attr:`obj` are set automatically. | ||
|
|
||
| .. versionchanged:: 3.10 |
There was a problem hiding this comment.
We dropped the .. versionchanged:: next note that documented the generated message. This is a user-visible change to str(), so I think we should keep a versionchanged here, no?
There was a problem hiding this comment.
@serhiy-storchaka, do you agree on bringing it back?
vstinner
left a comment
There was a problem hiding this comment.
The following code writes 'lazy_import' object has no attribute 'missing1': it mentions lazy_import rather than module 'asyncio'. But I don't think that AttributeError_str() would be the right place to reify the asyncio module!
lazy import asyncio
class RaiseWithName:
def __getattr__(self, name):
raise AttributeError(name)
try:
getattr(globals()['asyncio'], "missing1")
except AttributeError as exc:
# assert exc.obj is ...
assert exc.name == "missing1"
print(str(exc))
"this" and "current one" is unclear here. Please update the PR description to describe the motivation. Also consider linking to the code for the trick in KeyError. |
I used Claude to infer the motivations and update the description. |
This trick has been applied to KeyError and AttributeError; should it be applied to other exceptions? Should it be implemented in such a way that the concern is shared across exceptions (explicitly or implicitly)? |
Short answer, not really. Details(Analysis by Claude, on my behalf.) A worthwhile question. Worth noting first that the two "tricks" only look alike at the surface (both override
Only the second is the interesting reusable pattern ("I have structured context attributes; render them lazily instead of forcing every raise site to build the string"). The natural cohort there is So I'd lean against building a shared framework here:
The one genuinely reusable piece is the guard predicate — "was this constructed with a default/absent message, or one that merely duplicates a structured field, so I'm safe to override?" (here: |
@vstinner, this code does not trigger the code path from this PR ( |
Co-authored-by: Victor Stinner <vstinner@python.org>
Motivation
Code very often raises
AttributeError(name)or, more rarely, a bareAttributeErrorfrom__getattr__(there are ~40 such sites in CPython itself). In both cases the exception'sobjandnameattributes are populated automatically by_PyObject_SetAttributeErrorContext, but the message rendered in the traceback ignores them:raise AttributeError(name)shows just the attribute name, losing which object it was accessed on.raise AttributeErrorshows nothing at all.So today the message throws away context that the interpreter already has on hand (it uses
obj/namefor the "Did you mean …?" suggestions, but not for the message itself).This also produces the garbled output reported in #143811, where a bare
AttributeErrorrenders as an empty message glued to a suggestion:Change
This gives
AttributeErrora custom__str__(AttributeError_str) that synthesizes the familiar message from the collectedobj/namecontext:It is the same trick
KeyErroralready uses to wrap its key inrepr()— seeKeyError_str. Doing it inAttributeError.__str__(rather than intraceback) means every consumer of the message benefits, not just tracebacks.To avoid clobbering messages the caller actually meant, the generated message is only used when
objandnameare both set and the exception was constructed either with no positional arguments or with a single positional argument equal toname. An explanatory string passed by the caller (e.g.AttributeError("custom")) is preserved unchanged.As a side effect this also fixes #143811, which motivated the current issue — the
functools.singledispatchmethodcase now reports a proper message instead of the garbled one.AttributeErrormessages from their context #153785